﻿using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Collections.Generic;

public class ClientManager : MonoBehaviour {

	public static ManualResetEvent connectDone = new ManualResetEvent (false);
	public Button connectButton;
	public InputField ipInput;
	public string ipAddress;

	// delegate functions
	private delegate string getCommandDelegate ();
	private delegate void RespondDelegate(string x, string y);
	private delegate bool needsToRespondDelegate ();
	private RespondDelegate rdel;

	// commands
	private ICommand finishLevel;
	private ICommand levelStatus;
	private ICommand nameCommand;
	private ICommand texCommand;

	private Socket m_server;
	private static StateObject m_response;

	private static ClientManager instance = null;

	bool firstLoad = true;

	void Awake() {
		if (instance == null) {
			instance = this;
			Debug.Log ("Initialized CM");
		} else if (instance != this) {
			Destroy(this.gameObject);
		}
	}

	public static ClientManager getInstance() {
		return instance;
	}

	// Use this for initialization
	void Start () {
		Init ();
	}

	public void InitUI() {
		connectButton.interactable = false;
	}

	public void Init() {
		ClientManager.m_response = new StateObject ();
		ClientManager.m_response.received = true;

		// initialize commands
		finishLevel = new finishLevelCommand ();
		levelStatus = new levelStatusCommand ();
		nameCommand = new nameCommand ();
		texCommand = new textureCommand ();

		// setup delegates
		rdel += finishLevel.respond;
		rdel += levelStatus.respond;
		rdel += nameCommand.respond;
		//rdel += texCommand.respond;
	}
	
	// Update is called once per frame
	void Update () {
		if (ipInput != null) {
			if (ipInput.textComponent.text.Length > 0)
				connectButton.interactable = true;
		}
		if (m_server != null) {
			if(ClientManager.m_response.received == true) {
				string[] parts = new string[2];
				string str = m_response.sb.ToString();
				int idx = str.IndexOf(":");
				// Debug.Log (str + "\n" + idx);
				if(idx > 0) { 
					parts[0] = str.Substring(0, idx);
					parts[1] = str.Substring(idx+1);
					parts[1] = parts[1].Remove(parts[1].Length - 3, 2);
					// string[] parts = m_response.sb.ToString().Split(':');
					Debug.Log("Parts length - " + parts.Length);
					if(parts.Length == 2) {
						rdel(parts[0], parts[1]);
					}
				}
				// responseText.text = ClientManager.m_response.sb.ToString();
				ClientManager.Receive(m_server);
			}

			if(finishLevel.needsToRespond()) {
				ClientManager.Send(m_server, finishLevel.getCommand());
			}
			if(levelStatus.needsToRespond()) {
				ClientManager.Send(m_server, levelStatus.getCommand());
			}
			if(nameCommand.needsToRespond()) {
				ClientManager.Send(m_server, nameCommand.getCommand());
			}
			if(texCommand.needsToRespond()) {
				ClientManager.Send(m_server, texCommand.getCommand());
			}
			if(firstLoad) {
				firstLoad = false;
				Application.LoadLevel(3);
			}
		}
	}

	void OnDestroy() {
		Debug.Log ("CM will be destroyed");
		if (m_server != null) {
			m_server.Shutdown(SocketShutdown.Both);
		}
	}

	public void TestSendMessage() {
		ClientManager.Send (m_server, "echo:this is a test");

// 		ClientManager.Receive (m_server);
	}

	public void StartConnect() {
		m_server = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		ipAddress = ipInput.textComponent.text;
		Debug.Log ("Starting Connect to ~> " + ipAddress);
		IPAddress localAddr = IPAddress.Parse(ipAddress);
		Int32 port = 25002;
		IPEndPoint remoteEP = new IPEndPoint (localAddr, port);
		ClientManager.Connect (remoteEP, m_server);
		//ClientManager.Send (m_server, "connect:Srinu");
	}

	public static void Connect(EndPoint remoteEP, Socket client) {
		client.BeginConnect (remoteEP, new AsyncCallback (ConnectCallback), client);
		connectDone.WaitOne ();
	}

	private static void ConnectCallback(IAsyncResult ar) {
		try {
			Socket client = (Socket)ar.AsyncState;
			client.EndConnect (ar);
			Debug.Log ("Connected To " + client.RemoteEndPoint.ToString ());
			connectDone.Set ();
		} catch(Exception e) {
			Debug.Log(e.ToString());
		}
	}

	public static void Send(Socket client, String data) {
		data += "\r\n";
		byte[] encData = Encoding.ASCII.GetBytes (data);

		client.BeginSend (encData, 0, encData.Length, 0, new AsyncCallback (SendCallback), client);
		//sendDone.WaitOne ();
	}

	private static void SendCallback(IAsyncResult ar) {
		try {
			Socket client = (Socket) ar.AsyncState;
			int bytesSent = client.EndSend(ar);
			Debug.Log("Sent " + bytesSent + " To Server: " + client.RemoteEndPoint.ToString());
		} catch(Exception e) {
			Debug.Log(e.ToString());
		}
	}

	public static void Receive(Socket client) {
		try {
			m_response = new StateObject();
			m_response.workSocket = client;
			client.BeginReceive(m_response.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), m_response);
			// receiveDone.WaitOne();
		} catch(Exception e) {
			Debug.Log(e.ToString());
		}
	}

	private static void ReceiveCallback(IAsyncResult ar) {
		try {
			StateObject state = (StateObject)ar.AsyncState;
			Socket client = state.workSocket;

			int bytesRead = client.EndReceive(ar);
			if(bytesRead > 0) {
				state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
				if(state.sb.ToString().IndexOf("\r\n") > 0) {
					Debug.Log(state.sb.ToString());
					state.received = true;
				} else {
					client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
				}
			}
		} catch(Exception e) {
			Debug.Log(e.ToString());
		}
	}

	void OnLevelWasLoaded(int level) {
		if (level == 1) {
			Debug.Log("CM ~> Loading Level 1");
			ipInput = GameObject.Find("IPInput").GetComponent<InputField>();
			connectButton = GameObject.Find("ConnectButton").GetComponent<Button>();
		}
	}
}

public class StateObject {
	public Socket workSocket = null;
	public const int BufferSize = 1024;
	public byte[] buffer = new byte[BufferSize];
	public StringBuilder sb = new StringBuilder();
	public List<byte> bl = new List<byte> ();
	public bool received = false;
}
